﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace WindowsAPI.Processes
{
    public static class ProcessEx
    {
        public static void SetPrivileges(string Signatures = SE_Signatures.SE_SHUTDOWN_NAME)
        {
            Process.GetCurrentProcess().SetPrivileges(Signatures);
        }

        /// <summary>Установка привеллегий процессу</summary>
        /// <param name="process">Обрабатываемый процесс</param>
        /// <param name="Signatures">Устанавливаемая сигнатура. По умолчанию Signatures = SE_Signatures.SE_SHUTDOWN_NAME</param>
        /// <returns></returns>
        public static void SetPrivileges(this Process process, string Signatures = SE_Signatures.SE_SHUTDOWN_NAME)
        {
            IntPtr htok;

            if(!AdvAPI32.OpenProcessToken(Process.GetCurrentProcess().Handle,
                                          DesiredAccessType.TOKEN_ADJUST_PRIVILEGES | DesiredAccessType.TOKEN_QUERY,
                                          out htok))
                Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

            //заполняем поля структуры
            var tkp = new TOKEN_PRIVILEGES { PrivilegeCount = 1, Attributes = AdvAPI32.SE_PRIVILEGE_ENABLED, Luid = 0 };
            //получаем системный идентификатор необходимой нам привилегии
            if(!AdvAPI32.LookupPrivilegeValue(null, Signatures, out tkp.Luid))
                Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

            //повышем привилигеию своему процессу
            if(!AdvAPI32.AdjustTokenPrivileges(htok, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
                Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
        }

        //public static bool[] CheckPrivilages(this Process process, params string[] Privilage)
        //{
        //    var memory_size = Marshal.SizeOf(typeof(PRIVILEGE_SET))
        //                      + Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES)) * (Privilage.Length - 1);
        //    var Priv = new PRIVILEGE_SET();
        //    var pPriv = Marshal.AllocHGlobal(memory_size);

        //}

        public static Process[] GetProcesses(Predicate<Process> Filter = null)
        {
            var processes = Process.GetProcesses();
            if(Filter == null) return processes;
            var pList = new List<Process>(processes.Length);
            foreach(var p in processes)
                if(Filter(p))
                    pList.Add(p);
            return pList.ToArray();
        }

        public static FileInfo GetProcessFileInfo(int PID) { return GetProcessFileInfo(Process.GetProcessById(PID)); }

        public static FileInfo GetProcessFileInfo(this Process process)
        {
            var file_name_builder = new StringBuilder(1024);
            var hWnd = process.Handle;
            psapi.GetProcessImageFileName(hWnd, file_name_builder, file_name_builder.Capacity);
            var file_name = file_name_builder.ToString();
            //file_name = GetRealPath(file_name);

            //var stack = new List<string>(file_name.Split(Path.DirectorySeparatorChar));
            //var device = stack[2].Replace("HarddiskVolume", "");
            //var drive_index = -1;
            //if(Int32.TryParse(device, out drive_index))
            //{
            //    stack.RemoveRange(0, 2);
            //    stack[0] = DriveInfo.GetDrives()[drive_index - 2].Name.Split(Path.DirectorySeparatorChar)[0];
            //}
            //else
            //    stack.RemoveRange(0, 3);

            //file_name_builder = new StringBuilder(file_name_builder.Length);
            //for(var i = 0; i < stack.Count; i++)
            //    file_name_builder.AppendFormat("\\{0}", stack[i]);

            //file_name = file_name_builder.ToString().Remove(0, 1);

            DetectOpenFiles.ConvertDevicePathToDosPath(file_name, out file_name);

            return new FileInfo(file_name);
        }

        private static string GetRealPath(string path)
        {
            string realPath;
            var pathInformation = new StringBuilder(250);

            // Get the drive letter of the 
            var driveLetter = Path.GetPathRoot(path).Replace("\\", "");
            Kernel32.QueryDosDevice(driveLetter, pathInformation, 250);

            // If drive is substed, the result will be in the format of "\??\C:\RealPath\".
            if(pathInformation.ToString().Contains("\\??\\"))
            {
                // Strip the \??\ prefix.
                var realRoot = pathInformation.ToString().Remove(0, 4);

                //Combine the paths.
                realPath = Path.Combine(realRoot, path.Replace(Path.GetPathRoot(path), ""));
            }
            else
                realPath = path;
            return realPath;
        }

        //private static string DevicePathToWin32Path(string strDevicePath)
        //{
        //    const int MAX_PATH = 1024;
        //    char[] pszFilename = new char[MAX_PATH+2];
        //    var pszFilename_str = strDevicePath;
        //    //strncpy ( pszFilename, strDevicePath, MAX_PATH );
        //    //pszFilename[MAX_PATH] = 0;
        //    pszFilename_str = (char)0 + pszFilename_str.Substring(1);

        //    const int BUFSIZE = 1024;
        //    // Translate path with device name to drive letters.
        //    //TCHAR szTemp[BUFSIZE];
        //    var szTemp = new char[BUFSIZE];
        //    szTemp[0] = '\0';
        //    var szTemp_str = new string(new[] {'\0'});

        //    if(Kernel32.GetLogicalDriveStrings(BUFSIZE-1, szTemp))
        //    //if (GetLogicalDriveStrings(BUFSIZE-1, szTemp)) 
        //    {
        //        //TCHAR szName[MAX_PATH];
        //        var szNAme = new char[MAX_PATH];
        //        //TCHAR szDrive[3] = TEXT(" :");
        //        var szDrive = new char[] {' ', ':', '\0'}; 

        //        var bFound = false;
        //        //TCHAR* p = szTemp;
        //        var p = szTemp;

        //        do 
        //        {
        //            // Copy the drive letter to the template string
        //            szDrive = p;

        //            // Look up each device name
        //            if (Kernel32.QueryDosDevice(szDrive, szName, MAX_PATH))
        //            {
        //                UINT uNameLen = _tcslen(szName);

        //                if (uNameLen < MAX_PATH) 
        //                {
        //                    bFound = _tcsnicmp(pszFilename, szName, uNameLen) == 0;

        //                    if (bFound && *(pszFilename + uNameLen) == _T('\\')) 
        //                    {
        //                        // Reconstruct pszFilename using szTempFile
        //                        // Replace device path with DOS path
        //                        TCHAR szTempFile[MAX_PATH];
        //                        StringCchPrintf(szTempFile,
        //                        MAX_PATH,
        //                        TEXT("%s%s"),
        //                        szDrive,
        //                        pszFilename+uNameLen);
        //                        StringCchCopyN(pszFilename, MAX_PATH+1, szTempFile, _tcslen(szTempFile));
        //                    }
        //                }
        //            }
        //            // Go to the next NULL character.
        //            while (*p++);

        //        } while (!bFound && *p); // end of string
        //    }
        //    return pszFilename;
        //}


        //public static FileInfo[] GetProcessModules(Process process)
        //{
        //    var hModU = new uint[1024];
        //    var uiSizeU = (uint)(Marshal.SizeOf(typeof(uint)) * (hModU.Length));
        //    uint cbNeededU;

        //    if(!EnumProcessModules(process.Handle, hModU, uiSizeU, out cbNeededU))
        //        throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());

        //    var modules_count = (int)(cbNeededU / Marshal.SizeOf(typeof(uint)));

        //    var result = new FileInfo[modules_count];

        //    for(var i = 0; i < modules_count; i++)
        //    {
        //        var module_name = new StringBuilder(1024);
        //        GetModuleFileNameEx(process.Handle, (IntPtr)hModU[i], module_name, module_name.Capacity);
        //        result[i] = new FileInfo(module_name.ToString());
        //    }


        //    //var hMods = new IntPtr[1024];
        //    //using(var UOgch = new UsingObject<GCHandle>(GCHandle.Alloc(hMods, GCHandleType.Pinned), h => h.Free()))
        //    //{
        //    //    var pModules = UOgch.Object.AddrOfPinnedObject();
        //    //    var uiSize = (uint)(Marshal.SizeOf(typeof(IntPtr)) * (hMods.Length));
        //    //    uint cbNeeded;
        //    //    if(!EnumProcessModules(process.Handle, pModules, uiSize, out cbNeeded))
        //    //        throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
        //    //    var modules_count = (int)(cbNeeded / Marshal.SizeOf(typeof(IntPtr)));
        //    //    var result = new FileInfo[modules_count];
        //    //    for(var i = 0; i < modules_count; i++)
        //    //    {
        //    //        var module_name = new StringBuilder(1024);
        //    //        GetModuleFileNameEx(process.Handle, hMods[i], module_name, module_name.Capacity);
        //    //        result[i] = new FileInfo(module_name.ToString());
        //    //    }
        //    return result;
        //}



        //public static void GetProcessesModules(params Process[] Processes)
        //{
        //    foreach(var p in Processes)
        //    {
        //        // Setting up the variable for the second argument for EnumProcessModules
        //        var hMods = new IntPtr[1024];

        //        using(var UOgch = new UsingObject<GCHandle>(GCHandle.Alloc(hMods, GCHandleType.Pinned), h => h.Free()))
        //        {
        //            var gch = UOgch.Object;
        //            var pModules = gch.AddrOfPinnedObject();

        //            // Setting up the rest of the parameters for EnumProcessModules
        //            var uiSize = (uint)(Marshal.SizeOf(typeof(IntPtr)) * (hMods.Length));
        //            uint cbNeeded;

        //            if(!EnumProcessModules(p.Handle, pModules, uiSize, out cbNeeded)) continue;

        //            var uiTotalNumberofModules = (int)(cbNeeded / Marshal.SizeOf(typeof(IntPtr)));

        //            for(var i = 0; i < uiTotalNumberofModules; i++)
        //            {
        //                var strbld = new StringBuilder(1024);

        //                GetModuleFileNameEx(p.Handle, hMods[i], strbld, strbld.Capacity);
        //                Console.WriteLine("File Path: " + strbld);
        //                Console.WriteLine();
        //            }
        //            Console.WriteLine("Number of Modules: " + uiTotalNumberofModules);
        //            Console.WriteLine();
        //        }
        //    }

        public static Process CurrentParentProcess { get { return GetParentProcess(Process.GetCurrentProcess().Id); } }
        public static Process GetParentProcess(this Process p) { return GetParentProcess(p.Id); }
        public static Process GetParentProcess(int PID)
        {
            Process parentProc = null;
            var handleToSnapshot = IntPtr.Zero;
            try
            {
                var procEntry = ProcessEntry32.Default;
                handleToSnapshot = Kernel32.CreateToolhelp32Snapshot(SnapshotFlags.Process, 0);
                if(!Kernel32.Process32First(handleToSnapshot, ref procEntry))
                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                do
                {
                    if(PID == procEntry.th32ProcessID)
                        parentProc = Process.GetProcessById((int)procEntry.th32ParentProcessID);
                } while(Kernel32.Process32Next(handleToSnapshot, ref procEntry));
            }
            catch(Exception ex) { throw new ApplicationException("Can't get the process.", ex); }
            finally
            {
                // Must clean up the snapshot object!
                Kernel32.CloseHandle(handleToSnapshot);
            }
            return parentProc;
        }

        public static Process[] CurrentChildsProcesses { get { return GetChildsProcesses(Process.GetCurrentProcess().Id); } }
        public static Process[] GetChildsProcesses(this Process p) { return GetChildsProcesses(p.Id); }
        public static Process[] GetChildsProcesses(int PID)
        {
            var process_list = new List<Process>();
            var handleToSnapshot = IntPtr.Zero;
            try
            {
                var procEntry = ProcessEntry32.Default;
                handleToSnapshot = Kernel32.CreateToolhelp32Snapshot(SnapshotFlags.Process, 0);
                if(!Kernel32.Process32First(handleToSnapshot, ref procEntry))
                    Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());

                do
                {
                    if(PID == procEntry.th32ParentProcessID)
                        process_list.Add(Process.GetProcessById((int)procEntry.th32ProcessID));
                } while(Kernel32.Process32Next(handleToSnapshot, ref procEntry));
            }
            catch(Exception ex) { throw new ApplicationException("Can't get the process.", ex); }
            finally
            {
                // Must clean up the snapshot object!
                Kernel32.CloseHandle(handleToSnapshot);
            }
            return process_list.ToArray();
        }

        public static int GetParentProcessId()
        {
            var Information = new PROCESS_BASIC_INFORMATION();

            //Get a handle to our own process
            var CurrentProcessHandle = Kernel32.OpenProcess(ProcessAccessFlags.All, false, Process.GetCurrentProcess().Id);

            try
            {
                int sizeInfoReturned;
                var queryStatus = ntdll.NtQueryInformationProcess(
                    CurrentProcessHandle,
                    PROCESSINFOCLASS.ProcessBasicInformation,
                    ref Information,
                    Information.Size,
                    out sizeInfoReturned);
            }
            finally
            {
                if(!CurrentProcessHandle.Equals(IntPtr.Zero))
                {
                    //Close handle and free allocated memory
                    Kernel32.CloseHandle(CurrentProcessHandle);
                    //CurrentProcessHandle = IntPtr.Zero;
                }
            }

            return (int)Information.InheritedFromUniqueProcessId;
        }

        //public static void GetHandels()
        //{

        //}
    }
}
